//===-- RenderScriptExpressionOpts.cpp ------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include <string>

#include "llvm/ADT/None.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Module.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/Target/TargetOptions.h"

#include "clang/Basic/TargetOptions.h"

#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"

#include "RenderScriptExpressionOpts.h"
#include "RenderScriptRuntime.h"
#include "RenderScriptx86ABIFixups.h"

using namespace lldb_private;
using namespace lldb_renderscript;

// [``slang``](https://android.googlesource.com/platform/frameworks/compile/slang),
// the compiler frontend for RenderScript embeds an ARM specific triple in IR
// that is shipped in the app, after generating IR that has some assumptions
// that an ARM device is the target. As the IR is then compiled on a device of
// unknown (at time the IR was generated at least) architecture, when calling
// RenderScript API function as part of debugger expressions, we have to
// perform a fixup pass that removes those assumptions right before the module
// is sent to be generated by the llvm backend.

static bool registerRSDefaultTargetOpts(clang::TargetOptions &proto,
                                        const llvm::Triple::ArchType &arch) {
  switch (arch) {
  case llvm::Triple::ArchType::x86:
    proto.Triple = "i686--linux-android";
    proto.CPU = "atom";
    proto.Features.push_back("+long64");
    // Fallthrough for common x86 family features
    LLVM_FALLTHROUGH;
  case llvm::Triple::ArchType::x86_64:
    proto.Features.push_back("+mmx");
    proto.Features.push_back("+sse");
    proto.Features.push_back("+sse2");
    proto.Features.push_back("+sse3");
    proto.Features.push_back("+ssse3");
    proto.Features.push_back("+sse4.1");
    proto.Features.push_back("+sse4.2");
    break;
  case llvm::Triple::ArchType::mipsel:
    // pretend this is `arm' for the front-end
    proto.Triple = "armv7-none-linux-android";
    proto.CPU = "";
    proto.Features.push_back("+long64");
    break;
  case llvm::Triple::ArchType::mips64el:
    // pretend this is `aarch64' for the front-end
    proto.Triple = "aarch64-none-linux-android";
    proto.CPU = "";
    break;
  default:
    return false;
  }
  return true;
}

bool RenderScriptRuntimeModulePass::runOnModule(llvm::Module &module) {
  bool changed_module = false;
  Log *log = GetLog(LLDBLog::Language | LLDBLog::Expressions);

  std::string err;
  llvm::StringRef real_triple =
      m_process_ptr->GetTarget().GetArchitecture().GetTriple().getTriple();
  const llvm::Target *target_info =
      llvm::TargetRegistry::lookupTarget(std::string(real_triple), err);
  if (!target_info) {
    if (log)
      log->Warning("couldn't determine real target architecture: '%s'",
                   err.c_str());
    return false;
  }

  llvm::Optional<llvm::Reloc::Model> reloc_model = llvm::None;
  assert(m_process_ptr && "no available lldb process");
  switch (m_process_ptr->GetTarget().GetArchitecture().GetMachine()) {
  case llvm::Triple::ArchType::x86:
    changed_module |= fixupX86FunctionCalls(module);
    // For some reason this triple gets totally missed by the backend, and must
    // be set manually. There a reference in bcc/Main.cpp about auto feature-
    // detection being removed from LLVM3.5, but I can't see that discussion
    // anywhere public.
    real_triple = "i686--linux-android";
    break;
  case llvm::Triple::ArchType::x86_64:
    changed_module |= fixupX86_64FunctionCalls(module);
    break;
  case llvm::Triple::ArchType::mipsel:
  case llvm::Triple::ArchType::mips64el:
    // No actual IR fixup pass is needed on MIPS, but the datalayout and
    // targetmachine do need to be explicitly set.

    // bcc explicitly compiles MIPS code to use the static relocation model due
    // to an issue with relocations in mclinker. see
    // libbcc/support/CompilerConfig.cpp for details
    reloc_model = llvm::Reloc::Static;
    changed_module = true;
    break;
  case llvm::Triple::ArchType::arm:
  case llvm::Triple::ArchType::aarch64:
    // ARM subtargets need no fixup passes as they are the initial target as
    // generated by the
    // slang compiler frontend.
    break;
  default:
    if (log)
      log->Warning("Ignoring unknown renderscript target");
    return false;
  }

  if (changed_module) {
    llvm::TargetOptions options;
    llvm::TargetMachine *target_machine = target_info->createTargetMachine(
        real_triple, "", "", options, reloc_model);
    assert(target_machine &&
           "failed to identify RenderScriptRuntime target machine");
    // We've been using a triple and datalayout of some ARM variant all along,
    // so we need to let the backend know that this is no longer the case.
    if (log) {
      LLDB_LOGF(log, "%s - Changing RS target triple to '%s'", __FUNCTION__,
                real_triple.str().c_str());
      LLDB_LOGF(
          log, "%s - Changing RS datalayout to '%s'", __FUNCTION__,
          target_machine->createDataLayout().getStringRepresentation().c_str());
    }
    module.setTargetTriple(real_triple);
    module.setDataLayout(target_machine->createDataLayout());
  }
  return changed_module;
}

char RenderScriptRuntimeModulePass::ID = 0;

namespace lldb_private {

bool RenderScriptRuntime::GetOverrideExprOptions(clang::TargetOptions &proto) {
  auto *process = GetProcess();
  assert(process);
  return registerRSDefaultTargetOpts(
      proto, process->GetTarget().GetArchitecture().GetMachine());
}

bool RenderScriptRuntime::GetIRPasses(LLVMUserExpression::IRPasses &passes) {
  if (!m_ir_passes)
    m_ir_passes = new RSIRPasses(GetProcess());
  assert(m_ir_passes);

  passes.EarlyPasses = m_ir_passes->EarlyPasses;
  passes.LatePasses = m_ir_passes->LatePasses;

  return true;
}

namespace lldb_renderscript {

RSIRPasses::RSIRPasses(Process *process) {
  IRPasses();
  assert(process);

  EarlyPasses = std::make_shared<llvm::legacy::PassManager>();
  assert(EarlyPasses);
  EarlyPasses->add(new RenderScriptRuntimeModulePass(process));
}

RSIRPasses::~RSIRPasses() = default;

} // namespace lldb_renderscript
} // namespace lldb_private
